#pragma once
#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <etcd/Watcher.hpp>

#include <functional>

#include "logger.hpp"

namespace im{
    ///
    /// @brief 服务注册类
    /// @details 服务注册类，用于将服务注册到etcd中
    /// 
    class Registry {
    protected:
        const int TTL = 3;

    public:
        using ptr = std::shared_ptr<Registry>;

        Registry(const std::string& etch_host)
        :_client(std::make_shared<etcd::Client>(etch_host)),
        _keep_alive(_client->leasekeepalive(TTL).get()), _lease_id(_keep_alive->Lease()) {
        }

        bool register_server(const std::string& key, const std::string& value) {
            auto resp = _client->put(key, value, _lease_id).get();
            if (resp.is_ok() == false) {
                LOG_CRITICAL("服务注册失败!{}:{}", key, value);
                return false;
            }
            return true;
        }

    private:
        std::shared_ptr<etcd::Client> _client;
        std::shared_ptr<etcd::KeepAlive> _keep_alive;
        int64_t _lease_id; //租约id
    };

    ////
    /// @brief 服务发现类
    /// @details 用于从etcd中获取服务列表，并动态检测服务添加和删除变化
    class Discover {
        
    public:
        using ptr = std::shared_ptr<Discover>;
        using NotifyCallback = std::function<void(const std::string&, const std::string&)>;
        
        Discover(const std::string &etch_host, const std::string &base_dir, const NotifyCallback &put_cb, const NotifyCallback &del_cb)
        :_client(std::make_shared<etcd::Client>(etch_host)), _put_cb(put_cb), _del_cb(del_cb) {
            etcd::Response resp = _client->ls(base_dir).get();
            if (resp.is_ok() == false) {
                LOG_CRITICAL("获取服务信息数据失败：{}", resp.error_message());
            }
            int sz = resp.keys().size();
            for (int i = 0; i < sz; ++i) {
                if(_put_cb) {
                    _put_cb(resp.key(i), resp.value(i).as_string());
                }
            }

            _watcher = std::make_shared<etcd::Watcher>(*_client, base_dir, std::bind(&Discover::callback, this, std::placeholders::_1), true);
        }

        ~Discover() {
            _watcher->Cancel();
        }

    private:
        void callback(const etcd::Response& resp) {
            if(resp.is_ok() == false) {
                LOG_CRITICAL("watcher callback error: {}", resp.error_message());
            }

            for(const etcd::Event& ev : resp.events()) {
                if (ev.event_type() == etcd::Event::EventType::PUT) {
                    if (_put_cb)
                        _put_cb(ev.kv().key(), ev.kv().as_string());

                }else if (ev.event_type() == etcd::Event::EventType::DELETE_) {
                    if (_del_cb) 
                        _del_cb(ev.prev_kv().key(), ev.prev_kv().as_string());
                }
            }
        }

    private:
        NotifyCallback _put_cb = nullptr;
        NotifyCallback _del_cb = nullptr;
        std::shared_ptr<etcd::Client> _client;
        std::shared_ptr<etcd::Watcher> _watcher;
    };
}